using System;
using System.Windows.Forms;
using OhioState.Graphics;
using Tao.OpenGl;
using Tao.Platform.Windows;
using OhioState.Graphics.Proxy;

namespace OhioState.Graphics.OpenGL
{
    /// <summary> 
    /// A panel or form control that handles the OpenGL initialization,
    /// resizing, and rendering.
    /// </summary>
	public class OpenGLPanel : System.Windows.Forms.Control, IRenderPanel, IDisposable
	{
		//
		// Declare the RenderScene delegate and event
		//
		//public delegate void RenderScene( IRenderPanel panel );
		public event RenderScene renderScene;

		#region Background color
		//
		// Add a background color property.
		//
        private OSUColor backgroundColor;
		public void SetBackgroundColor( float red, float green, float blue )
		{
			backgroundColor.R = red;
			backgroundColor.G = green;
			backgroundColor.B = blue;
		}
		public OSUColor BackgroundColor
		{
			get{ return backgroundColor; }
			set
			{ 
				backgroundColor = value;
				this.Invalidate();
			}
		}
		#endregion Background color
		
        #region Frame counter
		private int frameCount = 0;
		public int FrameCount
		{
			get{ return frameCount; }
		}
        private bool _updateFrameCounter = true;
        public bool UpdateFrameCounter
        {
            get { return _updateFrameCounter; }
            set { _updateFrameCounter = value; }
        }
		#endregion Frame counter

        private IRenderResourceManager resourceManager;
        public IRenderResourceManager ResourceManager
        {
            get { return resourceManager; }
            set { resourceManager = value; }
        }
		//
		// Retain the Device and rendering contexts. You need to 
		// make sure the rendering context is current before setting
		// any OpenGL state. This allows for multiple OpenGL panels
		//
		private IntPtr deviceContext;                 // Private GDI Device Context
		private IntPtr renderContext;                 // Permanent Rendering Context
		//
		//
		// After resize, keep the aspect ratio. Also keep the zoom factor
		// for when we resize. This keeps things consistent.
		//
		protected  float				aspectRatio = 1.0f;

		public OpenGLPanel()
		{
			// This call is required by the Windows.Forms Form Designer.
			InitializeComponent();
            // Set up background color
            backgroundColor = new OSUColor();

			if( !this.InitOpenGL() )
			{
				System.Environment.Exit( -1 );
			}
			this.Capture = false;
        }

		#region Initialization
		protected virtual bool InitOpenGL()
		{
			int pixelFormat;                                                    // Holds The Results After Searching For A Match
			GC.Collect();                                                       // Request A Collection
			Gdi.PIXELFORMATDESCRIPTOR pfd = new Gdi.PIXELFORMATDESCRIPTOR();    // pfd Tells Windows How We Want Things To Be
			
			pfd.nSize = (short) System.Runtime.InteropServices.Marshal.SizeOf(pfd);      // Size Of This Pixel Format Descriptor
			
			pfd.nVersion = 1;                                                   // Version Number
			
			pfd.dwFlags = Gdi.PFD_DRAW_TO_WINDOW |                              // Format Must Support Window
		  				  Gdi.PFD_SUPPORT_OPENGL |                              // Format Must Support OpenGL
						  Gdi.PFD_DOUBLEBUFFER;                                 // Format Must Support Double Buffering

			pfd.iPixelType = (byte) Gdi.PFD_TYPE_RGBA;                          // Request An RGBA Format
			pfd.cColorBits = (byte) 32;                                         // Select Our Color Depth
			pfd.cRedBits = 0;                                                   // Color Bits Ignored
			pfd.cRedShift = 0;
			pfd.cGreenBits = 0;
			pfd.cGreenShift = 0;
			pfd.cBlueBits = 0;
			pfd.cBlueShift = 0;
			pfd.cAlphaBits = 0;                                                 // No Alpha Buffer
			pfd.cAlphaShift = 0;                                                // Shift Bit Ignored
			pfd.cAccumBits = 0;                                                 // No Accumulation Buffer
			pfd.cAccumRedBits = 0;                                              // Accumulation Bits Ignored
			pfd.cAccumGreenBits = 0;
			pfd.cAccumBlueBits = 0;
			pfd.cAccumAlphaBits = 0;
			pfd.cDepthBits = 16;                                                // 16Bit Z-Buffer (Depth Buffer)
			pfd.cStencilBits = 0;                                               // No Stencil Buffer
			pfd.cAuxBuffers = 0;                                                // No Auxiliary Buffer
			pfd.iLayerType = (byte) Gdi.PFD_MAIN_PLANE;                         // Main Drawing Layer
			pfd.bReserved = 0;                                                  // Reserved
			pfd.dwLayerMask = 0;                                                // Layer Masks Ignored
			pfd.dwVisibleMask = 0;
			pfd.dwDamageMask = 0;
			deviceContext = User.GetDC(this.Handle);                                      // Attempt To Get A Device Context
			if(deviceContext == IntPtr.Zero) 
			{                                            // Did We Get A Device Context?
				KillGLWindow();                          // Reset The Display
				MessageBox.Show("Can't Create A GL Device Context.", "ERROR",
					MessageBoxButtons.OK, MessageBoxIcon.Error);
				return false;
			}
			pixelFormat = Gdi.ChoosePixelFormat(deviceContext, ref pfd);                  // Attempt To Find An Appropriate Pixel Format
			if(pixelFormat == 0) 
			{                                              // Did Windows Find A Matching Pixel Format?
				KillGLWindow();                                                 // Reset The Display

				MessageBox.Show("Can't Find A Suitable PixelFormat.", "ERROR",
					MessageBoxButtons.OK, MessageBoxIcon.Error);
				return false;
			}
			if(!Gdi.SetPixelFormat(deviceContext, pixelFormat, ref pfd)) 
			{                // Are We Able To Set The Pixel Format?
				KillGLWindow();                                                 // Reset The Display

				MessageBox.Show("Can't Set The PixelFormat.", "ERROR",
					MessageBoxButtons.OK, MessageBoxIcon.Error);
				return false;
			}
			renderContext = Wgl.wglCreateContext(deviceContext);                                    // Attempt To Get The Rendering Context
			if(renderContext == IntPtr.Zero) 
			{                                            // Are We Able To Get A Rendering Context?
				KillGLWindow();                                                 // Reset The Display
				MessageBox.Show("Can't Create A GL Rendering Context.", "ERROR",
					MessageBoxButtons.OK, MessageBoxIcon.Error);
				return false;
			}
			if(!Wgl.wglMakeCurrent(deviceContext, renderContext)) 
			{                                 // Try To Activate The Rendering Context
				KillGLWindow();                                                 // Reset The Display
				MessageBox.Show("Can't Activate The GL Rendering Context.", "ERROR",
					MessageBoxButtons.OK, MessageBoxIcon.Error);
				return false;
			}

			if(!InitGL()) 
			{                                                     // Initialize Our Newly Created GL Window
				KillGLWindow();                                                 // Reset The Display
				MessageBox.Show("Initialization Failed.", "ERROR",
					MessageBoxButtons.OK, MessageBoxIcon.Error);
				return false;
			}
			//this.Resize += new EventHandler( OpenGLForm_Resize );
			this.Paint += new System.Windows.Forms.PaintEventHandler( OpenGLForm_PaintHandler );
			return true;
		}
		/// <summary>
		///     All setup for OpenGL goes here.
		/// </summary>
		/// <returns>
		///     <c>true</c> on successful initialization, otherwise <c>false</c>.
		/// </returns>
		private bool InitGL() {
			//Tao.OpenGl.GlExtensionLoader.LoadAllExtensions();					// Get all extensions 
			Gl.glShadeModel(Gl.GL_SMOOTH);                                      // Enable Smooth Shading
			//Gl.glEnable( Gl.GL_DEPTH_TEST );
			Gl.glEnable( Gl.GL_CULL_FACE );
			Gl.glClearColor(0, 0, 0, 0.5f);                                     // Black Background
			//Gl.glClearDepth(1.0);                                                 // Depth Buffer Setup
			//Gl.glDepthFunc(Gl.GL_LEQUAL);                                       // The Type Of Depth Testing To Do
			Gl.glHint(Gl.GL_PERSPECTIVE_CORRECTION_HINT, Gl.GL_NICEST);         // Really Nice Perspective Calculations
			return true;
		}
		/// <summary>
		///     Properly kill the window.
		/// </summary>
		private void KillGLWindow() {
			if(this.renderContext != IntPtr.Zero) {                                            // Do We Have A Rendering Context?
				if(!Wgl.wglMakeCurrent(IntPtr.Zero, IntPtr.Zero)) {             // Are We Able To Release The DC and RC Contexts?
					MessageBox.Show("Release Of DC And RC Failed.", "SHUTDOWN ERROR",
						MessageBoxButtons.OK, MessageBoxIcon.Error);
				}
				if(!Wgl.wglDeleteContext(this.renderContext)) {                                // Are We Able To Delete The RC?
					MessageBox.Show("Release Rendering Context Failed.", "SHUTDOWN ERROR",
						MessageBoxButtons.OK, MessageBoxIcon.Error);
				}
				this.renderContext = IntPtr.Zero;                                              // Set RC To Null
			}
			/*
			if(hDC != IntPtr.Zero) {                                            // Do We Have A Device Context?
				if(form != null && !form.IsDisposed) {                          // Do We Have A Window?
					if(form.Handle != IntPtr.Zero) {                            // Do We Have A Window Handle?
						if(!User.ReleaseDC(form.Handle, hDC)) {                 // Are We Able To Release The DC?
							MessageBox.Show("Release Device Context Failed.", "SHUTDOWN ERROR",
								MessageBoxButtons.OK, MessageBoxIcon.Error);
						}
					}
				}
				hDC = IntPtr.Zero;                                              // Set DC To Null
			}
			*/
			/*
			if(form != null) {                                                  // Do We Have A Windows Form?
				form.Hide();                                                    // Hide The Window
				form.Close();                                                   // Close The Form
				form = null;                                                    // Set form To Null
			}
			*/
		}
		#endregion Initialization

        protected void OpenGLForm_Resize( object sender, System.EventArgs e )
        {
            System.Windows.Forms.Control currentCtrl = (System.Windows.Forms.Control)sender;
            int width = currentCtrl.Width;
            int height = currentCtrl.Height;
            if ( 0 >= width || 0 >= height )
            {
                return;
            }
            Wgl.wglMakeCurrent( deviceContext, renderContext );

            // compute the aspect ratio
            // this will keep all dimension scales equal
            float aspect_ratio = (float) width / (float) height;
            Gl.glViewport(0, 0, width, height );
            float[] projectionMatrix = new float[16];
            Gl.glGetFloatv( Gl.GL_PROJECTION_MATRIX, projectionMatrix );
            Gl.glMatrixMode( Gl.GL_PROJECTION );
            Gl.glLoadIdentity();
            //
            // This is non-symmetric when adjusting the window's size.
            // We could multiply x by the 1/sqrt(aspectRatio) and 
            // y by sqrt(aspectRatio). Be consistent with projectioChanged.
            // Here, we need to go from the old aspect ratio to the new one,
            // so we scale accordingly.
            //
            Gl.glScalef( 1.0f, aspect_ratio / aspectRatio, 1.0f );
            Gl.glMultMatrixf( projectionMatrix );
            aspectRatio = aspect_ratio;
            Gl.glMatrixMode( Gl.GL_MODELVIEW );
            this.Invalidate();
        }

		#region Drawing
		protected override void OnPaintBackground( PaintEventArgs e )
		{ 
			//
			// This overrides the System.Windows.Forms.Control protected method
			// "OnPaintBackground()" and avoids clearing the client area of
			// this form window -- otherwise, OpenGL would flicker each frame.
			//
			Wgl.wglMakeCurrent( deviceContext, renderContext );
            Tao.OpenGl.Gl.glClearColor(backgroundColor.R, backgroundColor.G, backgroundColor.B, 1.0f);
            Gl.glClear(Gl.GL_COLOR_BUFFER_BIT | Gl.GL_DEPTH_BUFFER_BIT);
        }

        //DateTime startTime, endTime;
        protected void OpenGLForm_PaintHandler( object sender, System.Windows.Forms.PaintEventArgs e )
		{
			//
			// Keep track of the number of times this paint handler has been called.
			// This is useful (and lightweight) for determining framerates. An 
			// associated property allows a query on the frame count, and a timer 
			// can be used to provide frame rates.
			//
            if (_updateFrameCounter)
            {
                frameCount++;
            }
            //startTime = DateTime.Now;
			//
			// Make the OpenGL for this form the current rendering context.
			//
			Wgl.wglMakeCurrent( deviceContext, renderContext );
			//
			// Trigger the renderScene event to notify subscribers that they
			//   should render their scenes now.
			// Is this thread safe? Will the rendering context be the same?
			//
			if( renderScene != null )
			{
				Delegate[] renderSubscribers = renderScene.GetInvocationList();
                foreach( Delegate subscriber in renderSubscribers)
				{
					RenderScene renderEvent = subscriber as RenderScene;
					try 
					{
						//
						// Protect the user renderers from changing the camera state.
						//
						// With events, I can not ensure that one renderer does not leave
						// garbage in the OpenGL state for the next renderer, since OpenGL 
						// is unsafe and unmanaged.
						//
						Gl.glPushMatrix();
						//
						// We need to make sure any graphics methods are executed on the user interface thread.
						// This can cause deadlock if the thread is stopped waiting for an EndInvoke or something.
						//
                        if (this.InvokeRequired)
                        {
                            this.Invoke(renderEvent);
                        }
                        else
                        {
                            renderEvent(this);
                        }
						Gl.glPopMatrix();
					}
					catch(Exception exc)
					{
                        MessageBox.Show(exc.Message + exc.StackTrace);
                        throw;
						//
						// They say it is good programming to catch any exceptions.
						// What exceptions might a client throw? 
						// It could be anything in this context?
						// We could also check for errors.
						//
						// This empty catch avoids faulty (or malicious) clients from 
						//    crashing the publisher and even other clients.
						//
					};
				}
			}
			//
			// We should add another event here to capture the buffer 
			//    contents for anyone who is interested.
			// Also, we should only swap buffers if it is a double buffer
			//    which usually implies an on-screen buffer.
			// We want this code to work for off-screen buffers as well.
			//

			Wgl.wglSwapBuffers( deviceContext );
            //endTime = DateTime.Now;
            //TimeSpan duration = endTime - startTime;
            //Console.WriteLine(1000.0/(duration.TotalMilliseconds));
        }
		#endregion

        #region Shader compiling and linking
        public bool Compile_ThisThread(ShaderRoutine routine)
        {
            ShaderRoutineHandler handler = resourceManager.GetItem(routine) as ShaderRoutineHandler;
            if (handler != null)
            {
                return handler.Compile(this);
            }
            return false;
        }
        public bool Compile(ShaderRoutine routine)
        {
            if (this.InvokeRequired)
                return (bool) this.Invoke((MethodInvoker)delegate { this.Compile(routine); });
            else
                return this.Compile_ThisThread(routine);
        }
        public bool Link_ThisThread(ShaderProgram program)
        {
            ShaderProgramHandler handler = resourceManager.GetItem(program) as ShaderProgramHandler;
            if (handler != null)
            {
                bool linkSuccessful = handler.Build(this);
                program.Invalidate();
                return linkSuccessful;
            }
            return false;
        }
        public bool Link(ShaderProgram program)
        {
            if (this.InvokeRequired)
                return (bool)this.Invoke((MethodInvoker)delegate { this.Link(program); });
            else
                return this.Link_ThisThread(program);
        }
        #endregion

        #region IDisposable Members

        void IDisposable.Dispose()
        {
            Dispose(true);
            // This object will be cleaned up by the Dispose method.
            // Therefore, you should call GC.SupressFinalize to
            // take this object off the finalization queue
            // and prevent finalization code for this object
            // from executing a second time.
            GC.SuppressFinalize(this);
        }

        #endregion
        /// <summary> 
        /// Clean up any resources being used.
        /// </summary>
        protected override void Dispose(bool disposing)
		{
			if( disposing )
			{
				if(components != null)
				{
					components.Dispose();
				}
			}
            KillGLWindow();
            resourceManager = null;
            renderScene = null;

            base.Dispose(disposing);
		}

		#region Component Designer generated code
        private System.ComponentModel.Container components = null;
        /// <summary> 
		/// Required method for Designer support - do not modify 
		/// the contents of this method with the code editor.
		/// </summary>
		private void InitializeComponent()
		{
    	    components = new System.ComponentModel.Container();
		}
		#endregion
    }
}
